"""它是一个服务模块，提供与成员相关的功能."""
import hashlib
import os
import re
import secrets
from datetime import date, datetime, timedelta
from glob import glob
from typing import Optional, Tuple
from typing_extensions import Annotated

from fastapi import Depends, Request, UploadFile
from PIL import Image, UnidentifiedImageError
from sqlalchemy import select, update

from core.database import db_session
from core.exception import AlertException, JSONException
from core.models import Member
from lib.common import filter_words, get_client_ip, is_none_datetime, check_prohibit_words
from lib.member import get_next_open_date, hide_member_id
from lib.pbkdf2 import validate_password
from service import BaseService


class MemberService(BaseService):
    """
    它是一类提供会员相关服务的物种的输液。.
    - 会员信息查询, 认证, 包括状态验证等功能.

    ### Example

    ```python
        @router.get("/members/{mb_id}")
        async def read_member(
            member_service: Annotated[MemberService, Depends()],
            current_member: Annotated[Member, Depends(get_current_member)]
        ):
            return member_service.get_member_profile(current_member)
    ```
    """

    def __init__(self, request: Request, db: db_session):
        self.request = request
        self.db = db
        self.config = request.state.config

    def raise_exception(self, status_code: int = 400, detail: str = None, url: str = None):
        raise AlertException(detail, status_code, url)

    def create_member(self, data) -> Member:
        """生成会员信息."""
        member = Member(**data.__dict__)
        member.mb_level = getattr(self.config, "cf_register_level", 1)
        member.mb_login_ip = get_client_ip(self.request)
        # 邮件身份验证
        if getattr(self.config, "cf_use_email_certify", False):
            member.mb_email_certify2 = secrets.token_hex(16)  # 一次性身份验证密钥
        else:
            member.mb_email_certify = datetime.now()  # 完整的认证

        self.db.add(member)
        self.db.commit()

        return member

    def read_member(self, mb_id: str) -> Member:
        """查询会员信息。"""
        member = self.fetch_member_by_id(mb_id)
        if not member:
            self.raise_exception(
                status_code=404, detail=f"{mb_id} : 没有会员信息.")
        return member

    def authenticate_member(self, mb_id: str, password: str) -> Member:
        """
        验证您的密码以执行成员身份验证.
        - 没有会员信息的会员，被撤回或被屏蔽的会员无法查找.
        - 找不到未完成电子邮件身份验证的成员.
        """
        # Idie, 以确保您不告诉我您的密码是错误的。
        # self.fetch_member()不调用.
        member = self.fetch_member_by_id(mb_id)
        if not member or not validate_password(password, member.mb_password):
            self.raise_exception(
                status_code=403, detail="Ide或密码不正确.")

        is_active, message = self.is_activated(member)
        if not is_active:
            self.raise_exception(status_code=403, detail=message)

        is_certified, message = self.is_member_email_certified(member)
        if not is_certified:
            key = hashlib.md5(f"{member.mb_ip}{member.mb_datetime}".encode()).hexdigest()
                       #这里传入了参数是浏览器的/email_certify/update/{mb_id}/{key}
            url = self.request.url_for("certify_email_update_form", mb_id=mb_id, key=key) 
 
            self.raise_exception(status_code=403, detail=message, url=url)

        return member

    def get_member(self, mb_id: str) -> Member:
        """
查询当前会员信息。
-无法查询没有会员信息、退出或被阻止的会员。
-无法查看未完成电子邮件身份验证的会员。
        """
        member = self.read_member(mb_id)
        is_active, message = self.is_activated(member)
        if not is_active:
            self.raise_exception(status_code=403, detail=message)

        is_certified, message = self.is_member_email_certified(member)
        if not is_certified:
            self.raise_exception(status_code=403, detail=message)

        return member

    def read_email_non_certify_member(self, mb_id: str, key: str) -> Member:
        """
查看未通过电子邮件身份验证的会员信息。
-无法查询没有会员信息、退出或被阻止的会员。
-无法查看已认证的会员。
-如果邮件身份验证请求信息（key）无效，则无法查询。
        """
        member = self.read_member(mb_id)

        is_active, message = self.is_activated(member)
        if not is_active:
            self.raise_exception(status_code=403, detail=message)

        if not is_none_datetime(member.mb_email_certify):
            self.raise_exception(status_code=409, detail="您已经是认证会员.")

        if member.mb_email_certify2 != key:
            self.raise_exception(
                status_code=400, detail="邮件身份验证请求信息无效。")

        return member

    def get_member_profile(self, mb_id: str, current_member: Member) -> Member:
        """
查看会员配置文件信息。
-首席执行官或您的信息不确认是否公开。
-未设置是否公开信息的会员无法查询。
"""
        member = self.read_member(mb_id)
        admin_id = getattr(self.config, "cf_admin")
        # 如果您不是最高管理者，并且您不是您自己的信息，我们将检查信息是否被披露.
        if current_member.mb_id not in {admin_id, mb_id}:
            if not current_member.mb_open:
                self.raise_exception(
                    status_code=403,
                    detail="如果您不披露您的信息，您将无法查找其他信息.\
                            信息公开设置可从会员信息修正.")
            if not member.mb_open:
                self.raise_exception(
                    status_code=403, detail="未披露会员信息的会员.")

        return member

    def is_activated(self, member: Member) -> Tuple[bool, str]:
        """“请确认您是活跃会员。”"""
        if member.mb_leave_date or member.mb_intercept_date:
            return False, "当前登录成员是已被撤回或阻止的成员."
        return True, "这是一个正常的成员。."

    def is_member_email_certified(self, member: Member) -> Tuple[bool, str]:
        """检查您是否是已完成电子邮件身份验证的成员.“请确认您是已完成电子邮件身份验证的会员。”
"""
        if self.config.cf_use_email_certify and is_none_datetime(member.mb_email_certify):
            return False, f"{member.mb_email} 你必须通过邮件认证。."
        return True, "已完成电子邮件身份验证的成员."

    def update_member(self, member: Member, data: dict) -> Member:
    #    “修改会员信息。”“修改会员信息。”
        member_data = data.items()
        for key, value in member_data:
            if hasattr(member, key) and value is not None:
                setattr(member, key, value)
        self.db.commit()

        return member

    def update_member_point(self, mb_id: str, point: int) -> None:
        """修改会员积分。"""
        self.db.execute(
            update(Member).values(mb_point=point)
            .where(Member.mb_id == mb_id)
        )
        self.db.commit()

    def leave_member(self, member: Member):
        """
注销会员
-对会员信息进行注销处理，并记录注销日期。
-不会删除实际数据。
        """
        member.mb_leave_date = datetime.now().strftime("%Y%m%d")
        member.mb_memo = f"{member.mb_memo}\n{datetime.now().strftime('%Y-%m-%d')}撤回了"
        self.db.commit()

    def find_id(self, mb_name: str, mb_email: str) -> Member:
        """
查找会员ID。
-最高管理者除外。
-通过社交登录注册的会员无法找到ID。
        """
        from bbs.social import SocialAuthService

        admin_id = getattr(self.config, "cf_admin", "admin")
        member = self.db.scalar(
            select(Member).where(
                Member.mb_name == mb_name,
                Member.mb_email == mb_email,
                Member.mb_id != admin_id  # 高层管理人员被排除在外
            )
        )
        if not member:
            self.raise_exception(
                status_code=404, detail="没有成员与您输入的信息一致.")
        if SocialAuthService.check_exists_by_member_id(member.mb_id):
            self.raise_exception(
                status_code=400, detail="已加入社交登录的会员无法找到自己的身份.")

        return hide_member_id(member.mb_id), member.mb_datetime.strftime("%Y-%m-%d %H:%M:%S")

    def find_member_from_password_info(self, mb_id: str, mb_email: str) -> Member:
        """
        查找具有密码信息的成员.
        - 高层管理人员被排除在外.
        - 已加入社交登录的成员找不到密码.
        """
        from bbs.social import SocialAuthService

        admin_id = getattr(self.config, "cf_admin", "admin")
        member = self.db.scalar(
            select(Member).where(
                Member.mb_id == mb_id,
                Member.mb_email == mb_email,
                Member.mb_id != admin_id  # 高层管理人员被排除在外
            )
        )
        if not member:
            self.raise_exception(
                status_code=404, detail="没有成员与您输入的信息一致.")

        if SocialAuthService.check_exists_by_member_id(member.mb_id):
            self.raise_exception(
                status_code=400, detail="已加入社交登录的成员找不到密码.")

        # 保存密码重置令牌
        member.mb_lost_certify = secrets.token_hex(16)
        self.db.commit()
        self.db.refresh(member)

        return member

    def reset_password(self, mb_id: str, token: str, hash_password: str):
        """我们重置了密码."""
        member = self.read_member_by_lost_certify(mb_id, token)
        member.mb_password = hash_password
        member.mb_lost_certify = ""
        self.db.commit()

    def read_member_by_lost_certify(self, mb_id: str, token: str) -> Member:
        """我们查找会员信息以重置密码."""
        from bbs.social import SocialAuthService

        admin_id = getattr(self.config, "cf_admin", "admin")
        member = self.db.scalar(
            select(Member).where(
                Member.mb_id == mb_id,
                Member.mb_lost_certify == token,
                Member.mb_id != admin_id  # 高层管理人员被排除在外
            )
        )
        if not member:
            self.raise_exception(status_code=404, detail="这是一个无效的请求.")

        if SocialAuthService.check_exists_by_member_id(member.mb_id):
            self.raise_exception(
                status_code=400, detail="已加入社交登录的成员将无法重置其密码.")

        return member

    def fetch_member_by_id(self, mb_id: str) -> Member:
        """ID我们在数据库中查找会员信息."""
        return self.db.scalar(select(Member).where(Member.mb_id == mb_id))

    def fetch_member_by_nick(self, mb_nick: str) -> Member:
        """我们通过昵称在数据库中查找会员信息."""
        return self.db.scalar(select(Member).where(Member.mb_nick == mb_nick))

    def fetch_member_by_email(self, mb_email: str, mb_id: str = None) -> Member:
        """
        我们通过邮件地址在数据库中查找会员信息.
        
        Args:
            mb_email (str): 电子邮件地址
            mb_id (str, optional): 会员Id. Defaults to None.
                用于在更正会员信息时排除您自己的电子邮件

        Returns:
            Member: 会员信息
            Member: 회원 정보
        """
        query = select(Member).where(Member.mb_email == mb_email)
        if mb_id:
            query = query.where(Member.mb_id != mb_id)
        return self.db.scalar(query)


class MemberImageService(BaseService):
    """
    提供相关服务的会员形象拼版类。.
    """
    ICON_DIR = "data/member"
    IMAGE_DIR = "data/member_image"
    NO_IMAGE_PATH = "/static/img/no_profile.gif"

    def __init__(self, request: Request):
        self.request = request

    def raise_exception(self, status_code: int = 400, detail: str = None, url: str = None):
        raise AlertException(detail, status_code, url)

    @staticmethod
    def get_icon_path(mb_id: str) -> str:
# “”返回会员图标的图像路径。

# Args:
# mb_id（str，optional）：会员ID。Defaults to None.

# Returns:
# str:会员图标路径
# """
        directory = MemberImageService.ICON_DIR
        return MemberImageService._get_image_path(directory, mb_id)

    @staticmethod
    def get_image_path(mb_id: str) -> str:
        """返回成员图像路径.

        Args:
            mb_id (str, optional): 会员Id. Defaults to None.

        Returns:
            str: 成员映像路径
        """
        directory = MemberImageService.IMAGE_DIR
        return MemberImageService._get_image_path(directory, mb_id)

    @staticmethod
    def _get_image_path(directory: str, mb_id: str = None) -> str:
            # """
            #         返回图像路径的函数
            #         -会员图标/图像存储在身份的前两个目录中。
            #         Args:
            #             directory (str): 图像路径
            #             mb_id (str, optional): 会员Id.

            #         Returns:
            #             str: 图像路径
            # """
        if not mb_id:
            return MemberImageService.NO_IMAGE_PATH

        member_directory = os.path.join(directory, mb_id[:2])
        image_files = glob(os.path.join(member_directory, f"{mb_id}.*"))
        if image_files:
            mtime = os.path.getmtime(image_files[0])  # 为高速缓存添加文件校正时间 添加时间
            return f"/{image_files[0]}?{int(mtime)}"

        return MemberImageService.NO_IMAGE_PATH

    def update_image_file(
            self,
            mb_id: str,
            image_type: str,
            file: Optional[UploadFile] = None,
            is_delete: Optional[int] = 0) -> None:
        """
处理会员图标/图像文件的保存和删除

Args:
request:FastAPI Request对象
mb_id:会员ID
file:要上载的图标/图像文件
is_delete:是否删除图标/图像
        """
        directory = self.IMAGE_DIR if image_type == "image" else self.ICON_DIR
        sub_directory = mb_id[:2]
        image_directory = os.path.join(directory, sub_directory)
        image_obj = self._validate_and_open_image(file, image_type)

        if is_delete or image_obj:
            self._delete_existing_images(image_directory, mb_id)

        if image_obj:
            # 映像存储路径创建
            os.makedirs(image_directory, exist_ok=True)
            # 图像存储
            file_ext = image_obj.format.lower()
            save_path = os.path.join(image_directory, f"{mb_id}.{file_ext}")

            image_obj.save(save_path)
            image_obj.close()

    def _delete_existing_images(self, directory: str, mb_id: str):
        """处理删除现有图像文件"""
        existing_images = glob(os.path.join(directory, f"{mb_id}.*"))
        for image in existing_images:
            os.remove(image)

    def _save_image_file(self, file: UploadFile, directory: str, mb_id: str, image_type: str):
        """图像文件存储处理"""
        image_obj = self._validate_and_open_image(file, image_type)
        if image_obj:
            # 图像存储路径
            file_ext = image_obj.format.lower()
            save_path = os.path.join(directory, mb_id[:2], f"{mb_id}.{file_ext}")
            # 映像存储路径创建
            os.makedirs(os.path.dirname(save_path), exist_ok=True)
            # 图像存储
            image_obj.save(save_path)
            image_obj.close()

    def _validate_and_open_image(
            self,
            file: Optional[UploadFile],
            image_type: str) -> Optional[Image.Image]:
        """
        验证会员图像文件

        Args:
        file:要上载的图像文件
        image_type：图像类型（img，icon）

        Raises:
        self.raise_exception：如果不是图像文件
        """

        if not file or not file.filename:
            return None

        config = self.request.state.config
        img_ext_regex = getattr(config, "cf_image_extension", "")
        image_config = self._get_image_type_config(image_type)

        try:
            image_obj = Image.open(file.file)
        except UnidentifiedImageError:
            self.raise_exception(400, "它不是一个图像文件.")

        width, height = image_obj.size
        type_name = image_config['name']
        cf_size = image_config['cf_size']
        cf_width = image_config['cf_width']
        cf_height = image_config['cf_height']

        if cf_size and file.size > cf_size:
            self.raise_exception(
                400, f"{type_name} 容量是 {cf_size} 请少上传.")

        if (cf_width and width > cf_width) or (cf_height and height > cf_height):
            self.raise_exception(
                400, f"{type_name} 尺寸是 {cf_width}x{cf_height} 请少上传.")

        if img_ext_regex:
            if not re.match(fr".*\.({img_ext_regex})$", file.filename, re.IGNORECASE):
                img_ext_str = img_ext_regex.replace("|", ", ")
                self.raise_exception(400, f"{img_ext_str} 只能上传文件.")

        return image_obj

    def _get_image_type_config(self, image_type: str) -> dict:
        """返回基于图像类型的设置."""
        config = self.request.state.config
        img_type_dict = {
            'icon': {
                'name': '图标',
                'cf_size': config.cf_member_icon_size,
                'cf_width': config.cf_member_icon_width,
                'cf_height': config.cf_member_icon_height,
            },
            'image': {
                'name': '图像',
                'cf_size': config.cf_member_img_size,
                'cf_width': config.cf_member_img_width,
                'cf_height': config.cf_member_img_height,
            },
        }
        return img_type_dict[image_type]


class ValidateMember(BaseService):
    """
    提供成员信息验证服务的类.
    """
    def __init__(self,
                 request: Request,
                 db: db_session,
                 member_service: Annotated[MemberService, Depends()]):
        self.request = request
        self.db = db
        self.config = request.state.config
        self.member_service = member_service

    def raise_exception(self, status_code: int = 400, detail: str = None, url: str = None):
        raise AlertException(detail, status_code, url)

    def valid_id(self, mb_id: str) -> None:
        """ 检查是否可以加入会员

        Args:
            mb_id (str): Idi加入
        """
        member = self.member_service.fetch_member_by_id(mb_id)
        if member:
            self.raise_exception(409, "这是一个已经加入的Idi.")

        if word := check_prohibit_words(self.request, mb_id):
            self.raise_exception(403, f"这是一个不能用作想法的词. ({word})")

    def valid_name(self, mb_name: str) -> None:
        """检查可供会员使用的姓名

        Args:
            mb_name (str): 要加入的名称
        """
        if word := filter_words(self.request, mb_name):
            self.raise_exception(403, f"过滤名称中的单词({word})包括.")

    def valid_nickname(self, mb_nick: str) -> None:
        """ 可注册昵称识别检查

        Args:
            mb_nick : 要注册的昵称
        """
        member = self.member_service.fetch_member_by_nick(mb_nick)
        if member:
            self.raise_exception(409, "这是一个已经存在的昵称.")

        if prohibit_word := check_prohibit_words(self.request, mb_nick):
            self.raise_exception(403, f"这是一个不能用作昵称的词. ({prohibit_word})")

        if filter_word := filter_words(self.request, mb_nick):
            self.raise_exception(403, f"过滤昵称上的单词({filter_word})包括.")

    def valid_nickname_change_date(self, change_date: date = None) -> None:
        """ 检查是否可以更改昵称

        Args:
            latest_date (date, optional): 昵称更改日期. Defaults to None.
        """
        available_days = getattr(self.config, "cf_nick_modify", 0)

        if (change_date
                and not is_none_datetime(change_date)
                and available_days != 0):
            available_date = change_date + timedelta(days=available_days)
            if datetime.now().date() < available_date:
                available_str = available_date.strftime("%Y-%m-%d")
                self.raise_exception(403, f"{available_str} 在此之后，您可以更改昵称.")

    def valid_email(self, email: str, except_mb_id: str = None) -> None:
        """ 可注册的电子邮件识别检查

        Args:
            email (str): 电子邮件地址
        """
        if self.is_exists_email(email, except_mb_id):
            self.raise_exception(409, "这是一封已经加入的电子邮件.")

        if self.is_prohibit_email(email):
            self.raise_exception(403, "邮件域被禁止使用.")

    def valid_recommend(self, mb_recommend: str, mb_id: str = None) -> None:
        """检查是否存在参考Idi

        Args:
            mb_recommend (str): 推荐ID
        """
        if not mb_recommend:
            return None

        if mb_id and mb_recommend == mb_id:
            self.raise_exception(403, "您不能注册为推荐人.")

        member = self.member_service.fetch_member_by_id(mb_recommend)
        if not member:
            self.raise_exception(404, "收藏夹不存在.")
            self.raise_exception(404, "추천인 아이디가 존재하지 않습니다.")

        is_email_certified, message = self.member_service.is_member_email_certified(member)
        if not is_email_certified:
            self.raise_exception(404, "这是一个尚未完成电子邮件身份验证的推荐人.")

    def is_exists_email(self, email: str, mb_id: str = None) -> bool:
        """检查您的电子邮件是否已注册

        Args:
            email (str): 电子邮件地址
            mb_id (str, optional): 会员Id. Defaults to None.
                用于在更正会员信息时排除您自己的电子邮件

        Returns:
            bool: 如果电子邮件已注册， True, 或者也许 False
        """
        member = self.member_service.fetch_member_by_email(email, mb_id)
        if member:
            return True
        return False

    def is_prohibit_email(self, email: str) -> bool:
        """禁止邮件识别的检查

        Args:
            email (str): 电子邮件地址

        Returns:
            bool: 如果是被禁止的邮件， True, 或者也许 False
        """
        _, domain = email.split("@")

        cf_prohibit_email = getattr(self.config, "cf_prohibit_email", "")
        if cf_prohibit_email:
            prohibited_domains = [d.lower().strip() for d in cf_prohibit_email.split('\n')]
            if domain.lower() in prohibited_domains:
                return True

        return False

    def valid_open_change_date(self, change_date: date = None) -> None:
        """检查配置文件是否已打开或未更改"""
        if not self.is_open_change_date(change_date):
            open_date = get_next_open_date(self.request, change_date)
            self.raise_exception(403, f"配置文件披露更改为 {open_date} 之后，这是可能的.")

    def is_open_change_date(self, change_date: date = None) -> bool:
        """检查配置文件是否已打开或未更改

        Args:
            change_date (date): 更改是否发布配置文件的日期

        Returns:
            bool: 配置文件可以发布
        """
        available_days = getattr(self.config, "cf_open_modify", 0)

        if (change_date
                and not is_none_datetime(change_date)
                and available_days != 0):
            return change_date < (date.today() - timedelta(days=available_days))
        return True


class ValidateMemberAjax(ValidateMember):
    """
    会员 ajax 用于请求 MemberService 实现类.  
    """
    def raise_exception(self, status_code: int = 400, detail: str = None, url: str = None):
        raise JSONException(200, message=detail, success=False)
